/* 
 * Copyright (c) 2004-2005 Massachusetts Institute of Technology. This code was
 * developed as part of the Haystack (http://haystack.lcs.mit.edu/) research 
 * project at MIT. Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files (the 
 * "Software"), to deal in the Software without restriction, including without 
 * limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following conditions: 
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software. 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE. 
 */

/*
 * Created on Aug 12, 2005
 */
package edu.mit.csail.relo.jdt.browse;

import java.util.Timer;
import java.util.TimerTask;

import org.apache.log4j.Logger;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.openrdf.model.URI;

import edu.mit.csail.relo.agent.BrowseModel;
import edu.mit.csail.relo.jdt.ReloJDTPlugin;
import edu.mit.csail.relo.jdt.parts.MethodEditPart;
import edu.mit.csail.relo.jdt.util.ArraySingleElementSelectionDialog;
import edu.mit.csail.relo.modelBridge.Artifact;
import edu.mit.csail.relo.parts.ArtifactEditPart;
import edu.mit.csail.relo.parts.ReloController;
import edu.mit.csail.relo.ui.ReloAction;

/**
 * TODO: To make tracker robus (in order of dependencies):
 * 1] Support inheritance/call-hierarchy views
 * 2] Move common portions back into Relo-Core
 * @author vineet
 *
 */
public class JDTLinkedTracker extends JDTTracker {
    static final Logger logger = ReloJDTPlugin.getLogger(JDTLinkedTracker.class);

	private BrowseModel bm = null;
	private ReloController rc = null;

    public JDTLinkedTracker(BrowseModel _bm, ReloController _rc) {
		bm = _bm;
		rc = _rc;
    }

    // allow users to disable automatic expansion of methods 
	protected IAction embeddedMethodAction = null;
	
    public static class LinkedTrackerAction extends ReloAction {
    	JDTLinkedTracker linkedTracker = null;
    	@Override
    	public void initAction() {
			if (linkedTracker == null) linkedTracker = new JDTLinkedTracker(bm, rc);

			Timer t = new Timer();
    		t.schedule(new TimerTask() {
    			@Override
    			public void run() {
    				for (IContributionItem item : tbm.getItems()) {
    					if (EmbeddedMethodAction.embeddedMethodId.equals(item.getId())) {
    						linkedTracker.embeddedMethodAction = ((ActionContributionItem) item).getAction();
    					}
    				}
    			}
    		}, 1000);
    	}
		@Override
		public void run(IAction action) {
			if (action.isChecked()) {
				boolean ok = linkedTracker.addListeners();
				if (!ok) action.setChecked(false); // cancelled
			} else
				linkedTracker.removeListeners();
		}
    }
    


    // remove listeners when the editpart is closed
    private final IPartListener cleanupListener = new IPartListener() {
        public void partActivated(IWorkbenchPart part) {}
        public void partBroughtToTop(IWorkbenchPart part) {}
        public void partDeactivated(IWorkbenchPart part) {}
        public void partOpened(IWorkbenchPart part) {}
        public void partClosed(IWorkbenchPart part) {
            if (part != rc.getWorkbenchPart()) return;
            removeListeners();
        }
    };

    
    
    @Override
    // add multiple listener-types and provide selection dialog for selecting the right workbench
    protected boolean addListeners() {
        final IWorkbenchWindow[] wwin = PlatformUI.getWorkbench().getWorkbenchWindows();

        IWorkbenchWindow tgtWkbnchWin = wwin[0];
        if (wwin.length > 1) {
            ArraySingleElementSelectionDialog workbenchSelectionDlg 
                = new ArraySingleElementSelectionDialog(
                        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), 
                        "Listen:", 
                        wwin, 
                        new WorkbenchWindowLabelProvider());
            workbenchSelectionDlg.open();
            if (workbenchSelectionDlg.sel == null) return false;
            tgtWkbnchWin = (IWorkbenchWindow) workbenchSelectionDlg.sel;
        }

        tgtWkbnchWin.getSelectionService().addPostSelectionListener(selProcessor);
        tgtWkbnchWin.getPartService().addPartListener(cleanupListener);
        
        return true;
    }
    @Override
    // we need to remove the mouseMoveListener and the cleanupListener as well
    protected void removeListeners() {
        IWorkbenchWindow[] wwin = PlatformUI.getWorkbench().getWorkbenchWindows();
        for (IWorkbenchWindow tgtWkbnchWin : wwin) {
            tgtWkbnchWin.getSelectionService().removePostSelectionListener(selProcessor);
            tgtWkbnchWin.getPartService().removePartListener(cleanupListener);
        }
    }
    private final class WorkbenchWindowLabelProvider extends LabelProvider {
        @Override
        public String getText(Object element) {
            if (!(element instanceof IShellProvider)) return null;
            Shell shell = ((IShellProvider)element).getShell();
            return shell.getText();
        }
    }
    
    /**
     * 
     */
    ////////////////////////////////////////////////////////////
    /// Modify Selection Processing Code to:
    ///  Not worry about the navigation path; and instead
    ///  1> Add directly to the view
    ///  2> Expand method implementation in the linking editor
    ////////////////////////////////////////////////////////////
    
    // TODO: figure out why the below are not needed
    //private IJavaElement prevDeclaredElement;
    //private JavaEditor prevEditor;
    //private ITextSelection prevTextSelection;
    
    @Override
    protected void addNavigationItem(IWorkbenchPart selectedPart, Object selElement) {
        Artifact art = bm.getArtifact(selElement);
        logger.info("scheduling art for adding: " + art);

        if (art != null && rc.artCreatable(art)) {
        	scheduleNavItemForDelayedAddition(art, selectedPart);
        }
    }

    /**
	 * The goal here is to add items after a small delay (1s). This will allow
	 * items that the user randomly clicks on to not be added
	 */
    private Timer delayedNavItemAdder = null;
    private void scheduleNavItemForDelayedAddition(final Artifact art, final IWorkbenchPart currSelectedPart) {
    	if (delayedNavItemAdder != null) delayedNavItemAdder.cancel();

    	// @tag cmd-infrastructure: this should be made a command (with the
		// selection chained on). Being a command which will automatically do
		// the layout after execution. We will also likely want support for
		// something similar to a rc.delayedExecute
    	delayedNavItemAdder = runDelayedInUIThread(1000, new Runnable() {
			public void run() {
		        final ArtifactEditPart aep = rc.createOrFindQueriedArtifactEditPart(art);

		        // delay selection changes to after layout happens
		        runDelayedInUIThread(1000, new Runnable() {
					public void run() {
			            rc.getRoot().getViewer().deselectAll();
			            rc.getRoot().getViewer().appendSelection(aep);
					}});

	            //logger.error("Added artficact: ");
	            if (embeddedMethodAction.isChecked())
	            	openNavigatedArtifact( currSelectedPart, aep);
	            //rc.execute(cmd)getCommand(request)
	            //getViewer().getEditDomain().getCommandStack().execute(cmdWithLayout);
	            rc.execute(rc.getLayoutCmd());
			}
		});
	}

   
	private static Timer runDelayedInUIThread(int delay, final Runnable delayedRunnable) {
		Timer delayTimer = new Timer();
		TimerTask tt = new TimerTask() {
			@Override
			public void run() {
				Display.getDefault().asyncExec(new Runnable() {
					public void run() {
						delayedRunnable.run();
					}});
			}
		};
		delayTimer.schedule(tt, delay);
		return delayTimer;
	}

	MethodEditPart recentDeclaredMethod = null;
    private void openNavigatedArtifact(final IWorkbenchPart selectedPart, final ArtifactEditPart declaredAEP) {
        if (!(declaredAEP instanceof MethodEditPart)) return;
        if (recentDeclaredMethod == declaredAEP) return;
        if (recentDeclaredMethod != null) {
            if (recentDeclaredMethod.getDetailLevel() > recentDeclaredMethod.getMinimalDL())
                recentDeclaredMethod.suggestDetailLevelDecrease();
        }
        Runnable r = new Runnable() {
            public void run() {
                declaredAEP.suggestDetailLevelIncrease();
                selectedPart.setFocus();
            }
        };
    	Display.getDefault().timerExec(500, r);
        recentDeclaredMethod = (MethodEditPart) declaredAEP;
    }

    @Override
    protected void addNavigationPath(Object prevElement, URI addingRel, Object selElement) {
        Artifact srcArt = bm.getArtifact(prevElement);
        Artifact dstArt = bm.getArtifact(selElement);
        if (srcArt != null && dstArt != null)
        	rc.addRel(srcArt, addingRel, dstArt);
    }



}
